package check;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import models.action.Action;
import models.asl.Edge;
import models.asl.Graph;
import models.asl.Node;
import models.asl.NodeType;
import models.state.EnvStateGraph;
import models.state.State;
import models.state.Transition;

/**
 * The Class Checker.
 * 
 * @author Tiancheng Zhang
 * @version 1.0
 */
public class Checker {
    
    public Checker() {
        
    }
    /**
     * Check context condition required.
     *
     * @param action the action
     * @return true, if successful
     */
    public static Set<String> checkContextConditionRequired(Action action, EnvStateGraph graph) {
        Set<String> conditionRequired = new HashSet<String>();
        
        List<String> preConditions = action.getPreCondition();
        /*check to see if pre-condition of action exists in state transition model. if exists we need to check*/
        for(String preCond: preConditions) {
            List<State> bigStatesList = graph.getStates();
            for(State s : bigStatesList) {
                List<String> atomStateList = s.getStates();
                for(String as : atomStateList) {
                    //found precondition exists in state transition model
                    if(preCond.trim().replace("~", "").equalsIgnoreCase(as.trim().replace("~", ""))) {
                        conditionRequired.add(preCond);
                        break;
                    }
                }
                if(conditionRequired.contains(preCond)) {
                    break;
                }
            }
        }
        
        /*check to see if action can trigger state change. If exists, add source state as pre-condition*/
        List<Transition> transitions = graph.getTransitions();
        for(Transition tran : transitions) {
            //not for self-cycled transition
            if(tran.getFrom().equals(tran.getTo())) {
                continue;
            }
            List<String> actions = tran.getActions();
            for(String act : actions) {
                if(act.equalsIgnoreCase(action.getName())) {
                    List<String> fromStates = graph.findStateById(tran.getFrom()).getStates();
                    List<String> toStates = graph.findStateById(tran.getTo()).getStates();
                    for(String from : fromStates) {
                        for(String to: toStates) {
                            //check reversed
                            if(!from.trim().equalsIgnoreCase(to.trim())
                                    && from.trim().replace("~", "").equalsIgnoreCase(to.trim().replace("~", ""))) {
                                conditionRequired.add(from);
                                break;
                            }
                        }
                    }
                    //those changed states are preconditions of that action
                }
            }
        }
        System.err.println("------------>" + action.getName() + "-------" + conditionRequired.toString());
        return conditionRequired;
    }
    
    /**
     * Check context condition required.
     *
     * @param action the action
     * @return true, if successful
     */
    public static Map<Node, List<String>> checkContextConditionRequired(EnvStateGraph envGraph, Graph domainGraph, List<Action> actions) {
        Map<Node, List<String>> actionToContextConditonMap = new HashMap<Node, List<String>>();
        //find trigger node
        for(Node eachNode : domainGraph.getNodes()) {
            if(eachNode.getVisitedFlag() != null && eachNode.getVisitedFlag().equals(Boolean.FALSE)
                    && eachNode.getType() != null && eachNode.getType().equals(NodeType.TRIGGINGEVENT)) {
                Set<State> currentState = new HashSet<State>(envGraph.getStates());
                //set to visited
                eachNode.setVisitedFlag(true);
                Node pNode = eachNode;
                //update current state
                findCurrentState(pNode, envGraph, currentState, domainGraph);
                DFSSearch(pNode, domainGraph, currentState, envGraph, actionToContextConditonMap, actions);
            }
        }
        return actionToContextConditonMap;
    }
    
    
    
    private static void DFSSearch(Node pNode, Graph domainGraph, Set<State> currentState, EnvStateGraph envGraph,  Map<Node, List<String>> actionToContextConditonMap,
            List<Action> actions) {
        if(actionToContextConditonMap.isEmpty()) {
            System.out.println("--->State Size:" + currentState.size() + "----Node Name:" + pNode.getName());
            if(pNode.getType().equals(NodeType.ACTION)) {
                //for action node, when there are multiple nodes
                //currentState is the state before performing the action
                if(currentState.size() > 1) {
                    List<String> dependContext = calculateContextCondition(currentState, actions, pNode);
                    if(dependContext != null &&  !dependContext.isEmpty()) {
                        actionToContextConditonMap.put(pNode, dependContext);
                        return;
                    }
                }
            }
            findCurrentState(pNode, envGraph, currentState, domainGraph);
            
            if(pNode.getOutgoingEdges() != null && !pNode.getOutgoingEdges().isEmpty()) {
                for(Edge oe : pNode.getOutgoingEdges()) {
                    Node nextNode = domainGraph.findNodeById(oe.getDestinationId());
                    DFSSearch(nextNode, domainGraph, currentState, envGraph, actionToContextConditonMap, actions);
                }
                  
            } else {
                return;
            }
        } else {
            return;
        }
        
    }
    
    
    private static List<String> calculateContextCondition(Set<State> currentState, List<Action> actions, Node nextAct) {
        List<String> contextCond = new ArrayList<String>();
        for(Action act : actions) {
            if(act.getName().equals(nextAct.getName())) {
                for(String preCond : act.getPreCondition()) {
                    //if preCond can change in states
                    int count = 0;
                    for(State state : currentState) {
                        for(String atomState : state.getStates()) {
                            if(preCond.equals(atomState)) {
                                count ++;
                            }
                        }
                     }
                    //if precondition exists but not same size as each state
                    if(count != 0 && count != currentState.size()) {
                        contextCond.add(preCond);
                    }
                }
                break;
            }
        }
        return contextCond;
    }
    
    
    
    
    /**
     * Find current state.
     *
     * @param pNode the node
     * @param envGraph the env graph
     * @return the list
     */
    public static Set<State> findCurrentState(Node pNode, EnvStateGraph envGraph, Set<State> currentState, Graph domainGraph) {

        if(pNode.getType().equals(NodeType.CONTEXT_CONDITION)) {
            String []atomState = pNode.getName().trim().split("&");
            for(State bigState : envGraph.getStates()) {
                int count = atomState.length;
                for(String envS : bigState.getStates()) {
                    for(String s : atomState) {
                        if(envS.equalsIgnoreCase(s)) {
                            count--;
                        }
                    }
                }
                //if state covers every atom context condition
                if(count != 0) {
                    currentState.remove(bigState);
                }
            }
        } else if(pNode.getType().equals(NodeType.ACTION)) {
            //calculate states after action
            envGraph.findReachableStateAndUpdate(domainGraph.getAgentActions(), currentState);
            
            
        }
        return currentState;
    }

}
